package tms.util.jabber;

import org.jivesoftware.smack.*;
import org.jivesoftware.smack.filter.PacketTypeFilter;
import org.jivesoftware.smack.packet.Presence;
import tms.business.login.LoginService;
import tms.business.time.TimeService;
import tms.model.login.UserEntity;
import tms.model.profile.UserProfileEntity;
import tms.model.time.TimeEntity;
import tms.util.context.WebContext;
import tms.util.logging.Area;
import tms.util.logging.Logger;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.ejb.EJB;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import static tms.util.logging.AreaType.JABBER;

@ApplicationScoped
public class JabberManager {
    private static ConcurrentMap<String, JabberUser> jabberUsers = new ConcurrentHashMap<String, JabberUser>();
    private XMPPConnection connection;

    @Inject @Area(JABBER)
    Logger logger;

    @EJB
    TimeService timeService;

    public Collection<JabberUser> getJabberUsers() {
        return jabberUsers.values();
    }

    public void userProfileChanged(Integer userId, UserProfileEntity oldProfile, UserProfileEntity newProfile) {
        try {
            Roster roster = getConnection().getRoster();
            if (oldProfile.getJabberId() != null && !oldProfile.getJabberId().equals(newProfile.getJabberId())) {
                roster.removeEntry(roster.getEntry(oldProfile.getJabberId()));
                jabberUsers.remove(oldProfile.getJabberId());
            }

            String jabberId = newProfile.getJabberId();
            if (jabberId != null) {
                if (!newProfile.getJabberActive()) {
                    if (roster.getEntry(jabberId) != null) {
                        roster.removeEntry(roster.getEntry(jabberId));
                    }
                    jabberUsers.remove(jabberId);
                } else {
                    RosterEntry rosterEntry = roster.getEntry(jabberId);
                    if (rosterEntry == null) {
                        roster.createEntry(jabberId, jabberId, null);
                    }
                    JabberUser jabberUser = jabberUsers.get(jabberId);
                    if (jabberUser == null) {
                        TimeEntity timeEntity = timeService.getCurrentTime(userId, new Date());
                        boolean timePaused = (timeEntity != null && timeEntity.isPaused());
                        jabberUsers.put(jabberId, new JabberUser(userId, jabberId, JabberUser.Status.UNKNOWN, JabberUser.Status.parse(newProfile.getJabberPauseStatus()), timePaused));
                    } else {
                        synchronized (jabberUser) {
                            jabberUser.setPauseStatus(JabberUser.Status.parse(newProfile.getJabberPauseStatus()));
                        }
                    }
                }
            }
        } catch (XMPPException e) {
            logger.warning("Error changing user profile.", e);
        }
    }

    public void pauseUser(String jabberId) {
        JabberUser jabberUser = jabberUsers.get(jabberId);
        if (jabberUser != null) {
            synchronized (jabberUser) {
                jabberUser.setPaused(true);
            }
        }
    }

    public void unpauseUser(String jabberId) {
        JabberUser jabberUser = jabberUsers.get(jabberId);
        if (jabberUser != null) {
            synchronized (jabberUser) {
                jabberUser.setPaused(false);
            }
        }
    }

    public void sendMessage(String jabberId, String message) {
        try {
            ChatManager chatManager = getConnection().getChatManager();
            Chat chat = chatManager.createChat(jabberId, null);
            chat.sendMessage(message);
        } catch (XMPPException e) {
            logger.warning("Error sending Jabber message.", e);
        }
    }

    public boolean isConnectionReady() {
        boolean retVal = true;

        if (connection == null) {
            retVal = false;
        } else if (!connection.isConnected()) {
            retVal = false;
        } else if (!connection.isAuthenticated()) {
            retVal = false;
        }

        return retVal;
    }

    public void refreshConnection() {
        try {
            if (connection != null) {
                try {
                    if (!connection.isConnected()) {
                        logger.info("Try reconnect to server.");
                        synchronized (connection) {
                            connection.connect();
                        }
                    }
                    if (!connection.isAuthenticated()) {
                        logger.info("Try authenticate on server.");
                        synchronized (connection) {
                            connection.login(WebContext.getJabberConfig().getLogin(), WebContext.getJabberConfig().getPassword());
                        }
                    }
                } catch (XMPPException e) {
                    logger.warning("Error connecting and authenticating on Jabber server.");
                    throw e;
                }
            } else {
                initConnection();
            }
        } catch (XMPPException e) {
            logger.warning("Error during connection check.", e);
        }
    }

    /**
     * Update jabber user status in cache if user exists
     *
     * @param userId - jabber user id
     * @param newStatus - new user status
     */
    static void updateUserStatus(String userId, JabberUser.Status newStatus) {
        JabberUser jabberUser = jabberUsers.get(userId);
        if (jabberUser != null) {
            synchronized (jabberUser) {
                jabberUser.setStatus(newStatus);
            }
        }
    }


    public static void initJabberCache(LoginService loginService, TimeService timeService) {
        List<UserEntity> users = loginService.getAllUsers();
        for (UserEntity user : users) {
            if (user.getProfile().getJabberActive() && user.getProfile().getJabberId() != null && !user.getProfile().getJabberId().isEmpty()) {
                TimeEntity timeEntity = timeService.getCurrentTime(user.getId(), new Date());
                boolean timePaused = (timeEntity != null && timeEntity.isPaused());
                JabberUser jabberUser = new JabberUser(user.getId(), user.getProfile().getJabberId(), JabberUser.Status.UNKNOWN, JabberUser.Status.parse(user.getProfile().getJabberPauseStatus()), timePaused);
                jabberUsers.put(user.getProfile().getJabberId(), jabberUser);
            }
        }
    }

    @PostConstruct
    private void init() {
        logger.info("Begin post-construct JabberManager init.");
        try {
            initConnection();
            initRoster();
            logger.info("Post-construct JabberManager init successfull.");
        } catch (XMPPException e) {
            logger.warning("Error during init JabberManager.", e);
        }    
    }

    @PreDestroy
    private void destroy() {
        if (connection != null && connection.isConnected()) {
            connection.disconnect();
        }
        logger.info("Disconnected from server successfully.");
    }
    

    private XMPPConnection getConnection() throws XMPPException {
        if (!isConnectionReady()) {
            throw new XMPPException("Connection is not ready.");
        }
        return connection;
    }

    private synchronized void initConnection() throws XMPPException {
        logger.info("Begin connection init.");
        try {
            if (WebContext.getJabberConfig() != null) {
                connection = new XMPPConnection(WebContext.getJabberConfig().getServer());
                connection.connect();
                connection.addPacketListener(new JabberPacketListener(connection), new PacketTypeFilter(Presence.class));
                connection.login(WebContext.getJabberConfig().getLogin(), WebContext.getJabberConfig().getPassword());
            } else {
                logger.info("Web context is not initialized yet.");
                return;
            }
        } catch (XMPPException e) {
            logger.warning("Error connecting or authenticating on Jabber server.");
            throw e;
        }
        logger.info("Connection initialized successfully.");
    }

    private void initRoster() throws XMPPException {
        logger.info("Begin connection init Roster.");
        try {
            Roster roster = getConnection().getRoster();
            Map<String, RosterEntry> rosterEntryMap = new HashMap<String, RosterEntry>(roster.getEntries().size());
            for (RosterEntry rosterEntry : roster.getEntries()) {
                rosterEntryMap.put(rosterEntry.getUser(), rosterEntry);
            }
            for (JabberUser jabberUser : jabberUsers.values()) {
                RosterEntry rosterEntry = rosterEntryMap.get(jabberUser.getJabberId());
                if (rosterEntry == null) {
                    roster.createEntry(jabberUser.getJabberId(), jabberUser.getJabberId(), null);
                }
            }
        } catch (XMPPException e) {
            logger.warning( "Error init Roster.");
            throw e;
        }
        logger.info("Roster initialized successfully.");
    }


    public static class JabberUser {
        private int userSysId;
        private String jabberId;
        private Status status;
        private Status pauseStatus;
        private boolean paused;


        public JabberUser(int userSysId, String jabberId, Status status, Status pauseStatus, boolean paused) {
            this.userSysId = userSysId;
            this.jabberId = jabberId;
            this.status = status;
            this.pauseStatus = pauseStatus;
            this.paused = paused;
        }


        public int getUserSysId() {
            return userSysId;
        }

        public String getJabberId() {
            return jabberId;
        }

        public Status getStatus() {
            return status;
        }

        public Status getPauseStatus() {
            return pauseStatus;
        }
        
        public boolean isPaused() {
            return paused;
        }

        private void setJabberId(String jabberId) {
            this.jabberId = jabberId;
        }

        private void setStatus(Status status) {
            this.status = status;
        }

        private void setPauseStatus(Status pauseStatus) {
            this.pauseStatus = pauseStatus;
        }

        private void setPaused(boolean paused) {
            this.paused = paused;
        }


        public static enum Status {
            UNKNOWN,    // user status is unknown
            OFFLINE,    // user is offline
            ONLINE,     // user is online
            AWAY,       // user is away
            NA,         // user is not available
            DND,        // user is in do not disturb mode
            CHAT;       // user is free for chat


            public static Status parse(Presence.Type type, Presence.Mode mode) {
                Status retVal = UNKNOWN;

                switch (type) {
                    case available:
                        if (mode == null) {
                            retVal = ONLINE;
                        } else {
                            switch (mode) {
                                case dnd:
                                    retVal = DND;
                                    break;
                                case xa:
                                    retVal = NA;
                                    break;
                                case chat:
                                    retVal = CHAT;
                                    break;
                                case away:
                                    retVal = AWAY;
                                    break;
                            }
                        }
                        break;
                    case unavailable:
                        retVal = OFFLINE;
                        break;
                }

                return retVal;
            }

            public static Status parse(String status) {
                Status retVal = UNKNOWN;
                if (status != null) {
                    if (status.equals("ONLINE")) {
                        retVal = ONLINE;
                    } else if (status.equals("DND")) {
                        retVal = DND;
                    } else if (status.equals("NA")) {
                        retVal = NA;
                    } else if (status.equals("CHAT")) {
                        retVal = CHAT;
                    } else if (status.equals("AWAY")) {
                        retVal = AWAY;
                    } else if (status.equals("OFFLINE")) {
                        retVal = OFFLINE;
                    }
                }
                return retVal;
            }
        }
    }
}
